/**
 * Copyright (c) 2015-2017, Henry Yang 杨勇 (gismail@foxmail.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lambkit.plugin.activerecord.datasource;

import cn.hutool.core.util.StrUtil;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.lambkit.core.Lambkit;
import com.lambkit.core.exception.LambkitException;
import com.lambkit.db.DataSourcePlugin;
import com.lambkit.db.LambkitDataSource;
import com.lambkit.db.LambkitTableMapping;
import com.lambkit.db.TableMappingManager;
import com.lambkit.db.datasource.DataSourceConfig;
import com.lambkit.db.datasource.DataSourceFactory;
import com.lambkit.plugin.druid.DruidDataSourceFactory;
import io.shardingsphere.api.config.ShardingRuleConfiguration;
import io.shardingsphere.api.config.TableRuleConfiguration;
import io.shardingsphere.api.config.strategy.ShardingStrategyConfiguration;
import io.shardingsphere.core.keygen.KeyGenerator;
import io.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

public class DataSourcePluginBuilder {

	private DataSourceConfig datasourceConfig;

	public DataSourcePluginBuilder(DataSourceConfig datasourceConfig) {
		this.datasourceConfig = datasourceConfig;
	}

	public DataSourcePlugin build(LambkitDataSource datasource) {
		if (datasourceConfig.isShardingEnable()) {
			return createShardingDataSource(datasource);
		} else {
			return createRecordPlugin(datasourceConfig);
		}
	}

	private DataSourcePlugin createShardingDataSource(LambkitDataSource datasource) {
		String configName = datasourceConfig.getName();
		DataSourceShardingPlugin shardingPlugin = new DataSourceShardingPlugin(configName);
		Map<String, DataSource> dataSourceMap = new HashMap<>();

		if (datasourceConfig.getChildDatasourceConfigs() != null) {
			for (DataSourceConfig childConfig : datasourceConfig.getChildDatasourceConfigs()) {
				DataSourcePlugin ds = createDataSource(childConfig);
				shardingPlugin.add(ds);
				dataSourceMap.put(childConfig.getName(), ds.getDataSource());
			}
		}
		/**
		 * 可能只是分表，不分库
		 */
		else {
			DataSourcePlugin ds = createDataSource(datasourceConfig);
			shardingPlugin.add(ds);
			dataSourceMap.put(datasourceConfig.getName(), ds.getDataSource());
		}

		ShardingRuleConfiguration shardingRuleConfiguration = new ShardingRuleConfiguration();

		List<LambkitTableMapping> tableInfos = TableMappingManager.me().getTablesInfos(datasourceConfig.getTable(),
				datasourceConfig.getExcludeTable());
		StringBuilder bindTableGroups = new StringBuilder();
		for (LambkitTableMapping ti : tableInfos) {
			TableRuleConfiguration tableRuleConfiguration = getTableRuleConfiguration(ti);
			shardingRuleConfiguration.getTableRuleConfigs().add(tableRuleConfiguration);
			bindTableGroups.append(ti.getTableName()).append(",");
		}

		if (bindTableGroups.length() > 0) {
			bindTableGroups.deleteCharAt(bindTableGroups.length() - 1); // delete last char
			shardingRuleConfiguration.getBindingTableGroups().add(bindTableGroups.toString());
		}

		try {
			DataSource shardingDataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap,
					shardingRuleConfiguration, new HashMap<String, Object>(), new Properties());
			shardingPlugin.setShardingDataSource(shardingDataSource);

			ActiveRecordPlugin activeRecordPlugin = StrUtil.isNotBlank(configName)
					? new ActiveRecordPlugin(configName, shardingDataSource)
					: new ActiveRecordPlugin(shardingDataSource);
			shardingPlugin.setArp(activeRecordPlugin);
			return shardingPlugin;
		} catch (SQLException e) {
			throw new LambkitException(e);
		}
	}

	private TableRuleConfiguration getTableRuleConfiguration(LambkitTableMapping tableInfo) {
		TableRuleConfiguration tableRuleConfig = new TableRuleConfiguration();
		tableRuleConfig.setLogicTable(tableInfo.getTableName());

		if (StrUtil.isNotBlank(tableInfo.getActualDataNodes())) {
			tableRuleConfig.setActualDataNodes(tableInfo.getActualDataNodes());
		}

		if (tableInfo.getKeyGeneratorClass() != KeyGenerator.class) {
			tableRuleConfig.setKeyGenerator(Lambkit.get(tableInfo.getKeyGeneratorClass()));
		}

		if (StrUtil.isNotBlank(tableInfo.getKeyGeneratorColumnName())) {
			tableRuleConfig.setKeyGeneratorColumnName(tableInfo.getKeyGeneratorColumnName());
		}

		if (tableInfo.getDatabaseShardingStrategyConfig() != ShardingStrategyConfiguration.class) {
			tableRuleConfig
					.setDatabaseShardingStrategyConfig(Lambkit.get(tableInfo.getDatabaseShardingStrategyConfig()));
		}

		if (tableInfo.getTableShardingStrategyConfig() != ShardingStrategyConfiguration.class) {
			tableRuleConfig.setTableShardingStrategyConfig(Lambkit.get(tableInfo.getTableShardingStrategyConfig()));
		}

		return tableRuleConfig;
	}

	private DataSourcePlugin createDataSource(DataSourceConfig dataSourceConfig) {
		DataSourceFactory factory = Lambkit.get(dataSourceConfig.getFactory());
		if (factory == null) {
			factory = new DruidDataSourceFactory();
		}
		DataSourcePlugin plugin = factory.createDataSource(dataSourceConfig);
		return plugin;
	}

	private DataSourcePlugin createRecordPlugin(DataSourceConfig dataSourceConfig) {
		DataSourceFactory factory = null;
		if (StrUtil.isNotBlank(dataSourceConfig.getFactory())) {
			factory = Lambkit.get(dataSourceConfig.getFactory());
		}
		if (factory == null) {
			factory = new DruidDataSourceFactory();
		}
		return factory.createDatasourcePlugin(dataSourceConfig);
	}
}
